package com.anjie.powerproject.service.impl;

import com.alibaba.fastjson.JSON;
import com.anjie.powerproject.common.*;
import com.anjie.powerproject.config.DataCache;
import com.anjie.powerproject.config.DataObject;
import com.anjie.powerproject.config.DataSet;
import com.anjie.powerproject.config.ExecuteResult;
import com.anjie.powerproject.entity.DataDay;
import com.anjie.powerproject.entity.DeviceValue;
import com.anjie.powerproject.entity.StationInfo;
import com.anjie.powerproject.map.ModelMap;
import com.anjie.powerproject.pojo.dto.*;
import com.anjie.powerproject.entity.Model;
import com.anjie.powerproject.service.*;
import com.anjie.powerproject.strategy.pojo.DayWeather;
import com.anjie.powerproject.strategy.utils.DateUtils;
import com.anjie.powerproject.strategy.utils.DecimalFormatUtils;
import com.anjie.powerproject.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadLocalRandom;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
public class ElectricityModelServiceImpl implements ElectricityModelService {
    private static final Logger logger = LoggerFactory.getLogger(ElectricityModelServiceImpl.class);

    @Autowired
    ModelService modelService;

    @Autowired
    DeviceService deviceService;

    @Autowired
    ExecutorService executor;

    @Autowired
    DataLoader dataLoader;

    @Autowired
    DeviceValueService deviceValueService;

    @Autowired
    DataDayService dataDayService;

    @Autowired
    PreDataDayService preDataDayService;

    @Autowired
    DayWeatherForecastService dayWeatherForecastService;

    @Autowired
    StationInfoService stationInfoService;

    @Autowired
    DataCache dataCache;

    @Autowired
    MockDataUtil mockDataUtil;

    @Autowired
    ModelMap modelMap;

    @Autowired
    Environment env;

    @Override
    public List<ModelVersion> getall(String deviceCode) {
        String code = checkDeviceCode(deviceCode);

        List<Model> models = modelService.getAllModel(code, ModelTypeEnum.ELECTRICITY);
        return modelMap.toDTO(models);
    }

    private String checkDeviceCode(String deviceCode) {
        StationInfo station = stationInfoService.findStationInfoLimitFirst();
        return station.getBatteryCode();
    }

    @Override
    public CompareDTO compare(CompareQueryDTO query) {
        String code = checkDeviceCode(query.getDeviceCode());
        checkCompareParams(query);

        CompareDTO compareDTO = new CompareDTO();

        // 生成日期列表
        List<String> dateList = DateRange.getDateListOfDateRange(query.getStartTime(), query.getEndTime());
        
        // 读取原模型数据
        List<BigDecimal> sourceData = preDataDayService.getPreDataDayValueByCodeAndDateRange(dateList, code, query.getSourceModel(), query.getStartTime(), query.getEndTime());
        compareDTO.setSourceList(sourceData);

        // 读取目标模型数据
        List<BigDecimal> targetData = preDataDayService.getPreDataDayValueByCodeAndDateRange(dateList, code, query.getTargetModel(), query.getStartTime(), query.getEndTime());
        compareDTO.setTargetList(targetData);

        // 读取目标点位的历史真实数据
        List<BigDecimal> historyList = dataDayService.getDataDayValues(dateList, code, query.getStartTime(), query.getEndTime());
        compareDTO.setHistoryList(historyList);

        // 读取日期数据
        compareDTO.setDateList(dateList);

        BigDecimal[] analyseValues = new BigDecimal[2];
        analyseValues[0] = computeAnalyseValue(sourceData, historyList);
        analyseValues[1] = computeAnalyseValue(targetData, historyList);

        compareDTO.setAnalyseValues(analyseValues);

        return compareDTO;
    }

    private void checkCompareParams(CompareQueryDTO queryDTO) {
        Date startTime = queryDTO.getStartTime();
        Date endTime = queryDTO.getEndTime();
        //如果有一个为空，则
        if (startTime == null || endTime == null) {
            logger.error("输入时间有空值，不合法");
            throw new RuntimeException("输入时间有空值，不合法");
        }
        //如果开始时间大于结束时间，
        if (startTime.compareTo(endTime) > 0) {
            logger.error("开始时间大于结束时间，输入不合法");
            throw new RuntimeException("开始时间大于结束时间，输入不合法");
        }

        // 校验是否传递了源模型id和目标模型id
        Integer source = queryDTO.getSourceModel();
        Integer target = queryDTO.getTargetModel();
        if (source == null) {
            logger.error("源模型id不能为空");
            throw new RuntimeException("源模型id不能为空");
        }
        if (target == null) {
            logger.error("目标模型id不能为空");
            throw new RuntimeException("源模型id不能为空");
        }
    }

    private List<String> enToChs(List<String> columns) {
        List<String> chsColumns = new ArrayList<>();
        for (String col : columns) {
            chsColumns.add(Constants.COLUMN_MAP.get(col));
        }
        return chsColumns;
    }

    @Override
    public ProcessDTO process(String[] bases, ProcessCommandDTO command, MultipartFile[] multipartFiles, Integer modelId, FillStrategyEnum fillStratetgy) {
        return process(bases, command, multipartFiles, modelId, fillStratetgy, null);
    }

    @Override
    public ProcessDTO process(String[] bases, ProcessCommandDTO command, MultipartFile[] multipartFiles, Integer modelId, FillStrategyEnum fillStrategy, Double val) {
        checkProcessParams(command);
        String code = checkDeviceCode(command.getDeviceCode());

        ProcessDTO processDTO = new ProcessDTO();
        processDTO.setDataSession(DigestUtils.md5DigestAsHex(new Date().toString().getBytes(StandardCharsets.UTF_8)));

        // 构建训练集
        try {
            DataLoader.DataSet originDataSet = dataLoader.buildData(bases, command.getStartTime(), command.getEndTime(), code, fillStrategy, val);
            processDTO.setEnColumns(originDataSet.getHeader());
            processDTO.setColumns(enToChs(originDataSet.getHeader()));
            processDTO.setData(originDataSet.getData());

            int count = processDTO.getData().size();
            String base = "";
            File dirPath = new File(base + Constants.PYTHON_DIR + String.format(Constants.ELECTRIC_EXTRA_FILE_DIR, code));
            if (!dirPath.exists()) {
                dirPath.mkdirs();
            }
            for (String b : bases) {
                File file = new File(b);
                if (file.exists()) {
                    base = b;
                    break;
                }
            }
            System.out.println("base: "+base);
            //base weikong

            DataSet dataSet = new DataSet(new Date());
            // 如果存在附加数据
            if (multipartFiles.length > 0) {
                //保存数据
                String path = storeExtraFile(base, code, multipartFiles[0], randomExtraFileName(command.getStartTime(), command.getEndTime()));
                String newBase="";
                if(bases[0].charAt(0)=='/'){
                    //linux路径
                    newBase=bases[0]+"static/python/";
                }else {
                    newBase = bases[0].substring(0, bases[0].length() - 16) + "python/";
                    newBase = newBase.replace('\\', '/');
                }
//                System.out.println(newBase+path);
                dataSet.setExtraFilePath(newBase+path);

                List<CsvData> csvDataList = checkExtraFile(Collections.singletonList(base + Constants.PYTHON_DIR + path));
                mergeExtraData(processDTO, csvDataList);
            } else {
                // 如果不存在附加数据，并且modelId不为空，则查询model看是否之前创建的时候有扩展文件
                if (modelId != null) {
                    Model model = modelService.getModelById(modelId);
                    if (!StringUtils.isEmpty(model.getJoinPath())) {
                        List<CsvData> csvDataList = checkExtraFile(Collections.singletonList(base + Constants.PYTHON_DIR + model.getJoinPath()));
                        // 检查csvData里的数据是否包含了预测数据的时间段
                        boolean check = checkTimeSpan(csvDataList, command.getStartTime(), command.getEndTime());
                        if (check) {
                            mergeExtraData(processDTO, csvDataList);
                        } else {
                            throw new RuntimeException("请添加扩展文件");
                        }
                    }
                }
            }
            if (processDTO.getData().size() / (count + 0.0) < 0.9) {
                throw new RuntimeException("缺失扩展文件数据");
            }

            dataSet.setColumns(processDTO.getEnColumns());
            dataSet.setData(processDTO.getData());
            dataCache.save(processDTO.getDataSession(), dataSet);
        } catch (Exception ex) {
            if (modelId == null) {
                logger.error("构建训练集失败! command:{}", JSON.toJSONString(command), ex);
                throw new RuntimeException(String.format("构建训练集失败! %s", ex.getMessage()));
            } else {
                logger.error("构建预测集失败! command:{}", JSON.toJSONString(command), ex);
                throw new RuntimeException(String.format("构建预测集失败! %s", ex.getMessage()));
            }
        }

        return processDTO;
    }

    private boolean checkTimeSpan(List<CsvData> csvDataList, Date startTime, Date endTime) throws ParseException {
        for (CsvData csvData : csvDataList) {
            List<String> dateList = csvData.getData().stream().map(data -> data.get(0)).collect(Collectors.toList());
            for (String date : dateList) {
                Date time = DateUtils.ymdFormat.parse(date);
                if (DateUtils.between(time, startTime, endTime)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * 存储扩展文件
     */
    private String storeExtraFile(String base, String code, MultipartFile file, String extraFileName) throws IOException {
        String[] split = file.getOriginalFilename().split("\\.");
        if (split.length == 0) {
            throw new RuntimeException("上传的文件类型不合法");
        }

        String suffix = split[split.length - 1];
        extraFileName = extraFileName + "." + suffix;
        String extraFilePath = String.format("%s/%s", String.format(Constants.ELECTRIC_EXTRA_FILE_DIR, code), extraFileName);
        File dirPath = new File(base + Constants.PYTHON_DIR + String.format(Constants.ELECTRIC_EXTRA_FILE_DIR, code));
        if (!dirPath.exists()) {
            dirPath.mkdirs();
        }
        File extraFile = new File(base + Constants.PYTHON_DIR + extraFilePath);
        if (!extraFile.exists()) {
            extraFile.createNewFile();
        }

        FileOutputStream fos = new FileOutputStream(extraFile);
        fos.write(file.getBytes());
        fos.flush();
        return extraFilePath;
    }

    /**
     * 时间戳_开始时间_结束时间_随机数
     */
    private String randomExtraFileName(Date start, Date end) {
        int random = ThreadLocalRandom.current().nextInt(1000, 9999);
        return String.format("%d_%s_%s_%d", new Date().getTime(), DateUtils.ymdFormat2.format(start), DateUtils.ymdFormat2.format(end), random);
    }

    /**
     * 合并附加数据
     */
    private void mergeExtraData(ProcessDTO processDTO, List<CsvData> dataList) {
        // 进行数据合并
        for (CsvData csvData : dataList) {
            List<List<String>> processDataList = processDTO.getData();
            List<String> columns = processDTO.getColumns();
            List<String> enColumns = processDTO.getEnColumns();

            List<String> csvDataHeader = csvData.getHeader();
            columns.addAll(csvDataHeader.subList(1, csvDataHeader.size()));
            enColumns.addAll(csvDataHeader.subList(1, csvDataHeader.size()));

            List<List<String>> data = csvData.getData();
            Map<String, List<String>> dataMap = data.stream().filter(e -> !e.isEmpty() && e.get(0) != null).collect(Collectors.toMap(e -> e.get(0), e -> e));

            // 合并时以扩展数据为准
            List<List<String>> tempList = new ArrayList<>();
            for (List<String> processData : processDataList) {
                String dateTime = processData.get(0);
                List<String> rows = dataMap.get(dateTime);
                if (rows != null) {
                    processData.addAll(rows.subList(1, rows.size()));
                    tempList.add(processData);
                }
            }

            processDTO.setData(tempList);
        }
    }

    private void buildDataBody(List<String> dateList, List<DataDay> energyValues, List<DayWeather> dayWeathers, List<List<String>> data) throws ParseException {
        // 提取出能量map
        Map<String, BigDecimal> energyMap = energyValues.stream().collect(Collectors.toMap(e -> DateUtils.ymdFormat.format(e.getSdate()), DataDay::getData));
        // 提取出天气map
        Map<String, DayWeather> weatherMap = dayWeathers.stream().collect(Collectors.toMap(e -> DateUtils.ymdFormat.format(e.getSdate()), e -> e));

        // 拼装数据预处理结果
        for (String date : dateList) {
            List<String> row = new ArrayList<>();
            row.add(date);

            // 能量
            BigDecimal energy = energyMap.get(date);
            //row.add(formatDouble(energy));
            row.add(energy.toString());

            // 天气数据
            DayWeather weather = weatherMap.get(date);
            if (weather == null) {
                row.addAll(Arrays.asList(null, null, null, null, null, null, null));
            } else {
                row.addAll(formatDoubles(weather.getTemphigh(), weather.getTemplow(), weather.getTempavg(), weather.getWind(), weather.getHumidityHigh(), weather.getHumidityLow(), weather.getWeather()));
            }

            // 时间数据
            DateUtils.TimeProperty property = DateUtils.getPropertyByDateStr(date);
            row.add(property.getHoliday());
            row.add(property.getWeekOfYear());
            row.add(property.getSeason());
            row.add(property.getMonth());
            row.add(property.getDay());
            // 星期数独热码
            for (int i = 1; i <= 7; i++) {
                if (String.valueOf(i).equals(property.getDayOfWeek())) {
                    row.add("1");
                } else {
                    row.add("0");
                }
            }
            // 供冷季、供热季
            int month = Integer.parseInt(property.getMonth());
            int day = Integer.parseInt(property.getDay());
            if ((month > 10) || (month < 4) || (month == 10 && day >= 18) || (month == 4 && day <= 1)) {
                row.add("1");
            } else {
                row.add("0");
            }
            if ((month > 6 && month < 9) || (month == 6 && day >= 8) || (month == 9 && day <= 22)) {
                row.add("1");
            } else {
                row.add("0");
            }

            data.add(row);
        }
    }

    private String formatDouble(Double val) {
        return val == null ? null : val.toString();
    }

    private List<String> formatDoubles(Double... vals) {
        List<String> doubles = new ArrayList<>();
        for (Double val : vals) {
            doubles.add(formatDouble(val));
        }
        return doubles;
    }

    private void checkProcessParams(ProcessCommandDTO command) {
        Date startTime = command.getStartTime();
        Date endTime = command.getEndTime();
        //如果有一个为空，则
        if (startTime == null || endTime == null) {
            logger.error("输入时间有空值，不合法");
            throw new RuntimeException("输入时间有空值，不合法，获取电量模型比较数据失败");
        }
        //如果开始时间大于结束时间，
        if (startTime.compareTo(endTime) > 0) {
            logger.error("开始时间大于结束时间，输入不合法");
            throw new RuntimeException("开始时间大于结束时间，输入不合法，获取电量模型比较数据失败");
        }
    }

    private List<CsvData> checkExtraFile(List<String> filePaths) throws IOException, ParseException {
        List<CsvData> csvDataList = new ArrayList<>();
        for (String filePath : filePaths) {
            CsvData data = null;
            try {
                File file = new File(filePath);
                if (file.getName().endsWith(".xlsx") || file.getName().endsWith(".xls")) {
                    data = ExcelUtil.load(new FileInputStream(file));
                } else if (file.getName().endsWith(".csv")) {
                    data = CsvUtil.loadCsv(file);
                }
            } catch (Exception e) {
                logger.error("读取附加文件失败:{}", e.getMessage(), e);
                throw new RuntimeException("读取附加文件失败:" + e.getMessage());
            }

            if (data == null) {
                logger.error("读取附加文件失败");
                throw new RuntimeException("读取附加文件失败");
            }

            List<List<String>> rows = data.getData();
            if (rows.isEmpty()) {
                logger.error("附加文件读取失败，或者附加文件中数据行为空");
                throw new RuntimeException("附加文件读取失败，或者附加文件中数据行为空");
            }

            //统一转换日期格式
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            for (int i=0;i<rows.size();i++){
                if(rows.get(i).get(0).contains("/")) {
                    Date date = new SimpleDateFormat("yyyy/M/dd").parse(rows.get(i).get(0));
                    rows.get(i).set(0, sdf.format(date));
                }
            }

            List<String> colOne = rows.stream().map(e -> e.size() > 0 ? e.get(0) : null).collect(Collectors.toList());

            // 校验第一列是否全部是日期格式（使用正则表达式）
            for (int i = 1; i < colOne.size(); i++) {
                if (!Pattern.matches(Constants.DATA_RE_STR, colOne.get(i))) {
                    logger.error("附加文件中第{}行数据的首位元素是非日期元素", i);
//                    String errorMsg=colOne.get(0);
                    throw new RuntimeException(String.format("附加文件中第%s行数据的首位元素是非日期元素", i));
//                    throw new RuntimeException(String.format("附加文件中第%s行数据的首位元素是非日期元素"+"，当前日期格式："+errorMsg, i));
                }
            }

            csvDataList.add(data);
        }

        return csvDataList;
    }

    /**
     * @deprecated
     */
    private List<CsvData> checkExtraFile(MultipartFile[] multipartFiles) throws ParseException {
        List<CsvData> csvDataList = new ArrayList<>();
        for (MultipartFile file : multipartFiles) {
            CsvData data = null;
            try {
                File tempFile = null;
                if (file.getOriginalFilename().endsWith(".xlsx")) {
                    tempFile = File.createTempFile(String.valueOf(new Date().getTime()), ".xlsx");
                } else if (file.getOriginalFilename().endsWith(".xls")) {
                    tempFile = File.createTempFile(String.valueOf(new Date().getTime()), ".xls");
                } else if (file.getOriginalFilename().endsWith(".csv")) {
                    tempFile = File.createTempFile(String.valueOf(new Date().getTime()), ".csv");
                }

                if (tempFile == null) {
                    throw new RuntimeException("不支持的文件类型");
                }

                FileOutputStream fos = new FileOutputStream(tempFile);
                fos.write(file.getBytes());
                fos.flush();
                tempFile.deleteOnExit();

                if (file.getOriginalFilename().endsWith(".xlsx") || file.getOriginalFilename().endsWith(".xls")) {
                    data = ExcelUtil.load(new FileInputStream(tempFile));
                } else if (file.getOriginalFilename().endsWith(".csv")) {
                    data = CsvUtil.loadCsv(tempFile);
                }
            } catch (Exception e) {
                logger.error("读取附加文件失败:{}", e.getMessage(), e);
                throw new RuntimeException("读取附加文件失败:" + e.getMessage());
            }

            if (data == null) {
                logger.error("读取附加文件失败");
                throw new RuntimeException("读取附加文件失败");
            }

            List<List<String>> rows = data.getData();
            if (rows.isEmpty()) {
                logger.error("附加文件读取失败，或者附加文件中数据行为空");
                throw new RuntimeException("附加文件读取失败，或者附加文件中数据行为空");
            }
            //统一转换日期格式
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            for (int i=0;i<rows.size();i++){
                if(rows.get(i).get(0).contains("/")) {
                    Date date = new SimpleDateFormat("yyyy/M/dd").parse(rows.get(i).get(0));
                    rows.get(i).set(0, sdf.format(date));
                }
            }

            List<String> colOne = rows.stream().map(e -> e.size() > 0 ? e.get(0) : null).collect(Collectors.toList());

            // 校验第一列是否全部是日期格式（使用正则表达式）
            for (int i = 1; i < colOne.size(); i++) {
                if (!Pattern.matches(Constants.DATA_RE_STR, colOne.get(i))) {
                    logger.error("附加文件中第{}行数据的首位元素是非日期元素", i);
//                    String errorMsg=colOne.get(0);
                    throw new RuntimeException(String.format("附加文件中第%s行数据的首位元素是非日期元素",i));
//                    throw new RuntimeException(String.format("附加文件中第%s行数据的首位元素是非日期元素"+"，当前日期格式："+errorMsg, i));
                }
            }

            csvDataList.add(data);
        }
        return csvDataList;
    }

    private String[] buildDataFileNameAndModelFileName(String code) {
        long now = new Date().getTime();
        String[] paths = new String[2];
        paths[0] = code + now + ".pickle";
        paths[1] = code + "_train" + now + ".csv";

        return paths;
    }

    private String buildPredictDatatFileName(String code) {
        long now = new Date().getTime();
        return code + "_test" + now + ".csv";
    }

    @Override
    public synchronized CreateDTO create(String[] bases, ModelCreateCommandDTO command) {
        StationInfo stationInfo = stationInfoService.findStationInfoLimitFirst();
        String code = stationInfo.getBatteryCode();
        Integer pointId=stationInfo.getBatteryPointId();

        String dataSession = command.getDataSession();
        // 从内存中取出数据
        DataObject dataObject = dataCache.load(dataSession);
        if (dataObject == null) {
            throw new RuntimeException("预处理数据不存在，请重新进行数据预处理");
        }
        DataSet dataSet = (DataSet) dataObject;

        Model model = new Model();
        model.setDeviceCode(code);
        model.setPointId(pointId);
        model.setModelName(command.getModelName());
        model.setModelInfo(command.getModelInfo());
        model.setCreateTime(new Date());
        model.setIsUsed(0);
        model.setType(ModelTypeEnum.ELECTRICITY.getCode());
//        model.setStatus(CreateStatusEnum.CREATEING.getCode());
        model.setStatus(CreateStatusEnum.COMPLETE.getCode());

        // 如果有扩展数据
        if (dataSet.getExtraFilePath() != null) {
            model.setIsJoin(1);
            model.setJoinPath(dataSet.getExtraFilePath());
        } else {
            model.setIsJoin(0);
        }

        String[] names = buildDataFileNameAndModelFileName(code);
        model.setModelPath(String.format("%s/%s", String.format(Constants.ELECTRIC_MODEL_DIR, code), names[0]));
        model.setDataPath(String.format("%s/%s", String.format(Constants.ELECTRIC_CREATE_DATA_DIR, code), names[1]));

        //查询设备名是否重复
        if(modelService.getCountModelByName(model.getModelName())>0){
            //设备名重复，报错
            CreateDTO createDTO = new CreateDTO();
            createDTO.setModelVersion(-1);
            return createDTO;
        }else {
            modelService.save(model);

            String base = "";
            for (String b : bases) {
                File file = new File(b);
                if (file.exists()) {
                    base = b;
                    break;
                }
            }

            // 这里另外开一个线程后台运行
            String finalBase = base;
//            System.out.println("finalBase:"+finalBase);
            modelService.train(finalBase, dataSet, model.getId(), model.getDataPath());

            CreateDTO createDTO = new CreateDTO();
            createDTO.setModelVersion(model.getId());
            return createDTO;
        }

    }

    @Override
    public synchronized AnalyseDTO predict(String[] bases, ModelPredictCommandDTO command) {
        // 首先从内存中查询调用python的预测脚本是否执行完成
        ExecuteResult executeResult = dataCache.loadExecuteResult(command.getModelVersion());
        AnalyseDTO analyseDTO = new AnalyseDTO();

        StationInfo stationInfo = stationInfoService.findStationInfoLimitFirst();
        String code = stationInfo.getBatteryCode();

        Model model = modelService.getModelById(command.getModelVersion());

        // 如果是点击按钮触发的，如果内存中存在该model相关的任务，则停止该任务，并且从dataCache中去除原始任务
        if (command.getFlag() != null && command.getFlag()) {
            if (executeResult != null) {
                if (executeResult.getComplete()) {
                    dataCache.removeExecuteResult(command.getModelVersion());
                    executeResult.getTask().cancel(true);
                    executeResult = null;
                } else {
                    analyseDTO.setComplete(true);
                    analyseDTO.setError("其他用户正在使用该模型");
                    return analyseDTO;
                }
            }
        }

        if (executeResult == null) {
            // 此处表示还未开始执行，这里会异步开启一个线程调用python脚本
            String dataSession = command.getDataSession();
            // 从内存中取出数据
            DataObject dataObject = dataCache.load(dataSession);
            if (dataObject == null) {
                throw new RuntimeException("预处理数据不存在，请重新进行数据预处理");
            }
            DataSet dataSet = (DataSet) dataObject;
            if (CollectionUtils.isEmpty(dataSet.getData())) {
                throw new RuntimeException("预处理数据为空");
            }
            String base = "";
            for (String b : bases) {
                File file = new File(b);
                if (file.exists()) {
                    base = b;
                    break;
                }
            }

            executeResult = new ExecuteResult(false);
            executeResult.setModelType(ModelTypeEnum.ELECTRICITY);
            executeResult.setDeviceCode(code);

            // 这里另外开一个线程后台运行
            String finalBase = base;
            Integer modelId = model.getId();
            String dataPath = String.format("%s/%s", String.format(Constants.ELECTRIC_PREDICT_DATA_DIR, code), buildPredictDatatFileName(code));
            Future<?> task = executor.submit(() -> {
                try {
                    modelService.predict(finalBase, dataSet, modelId, dataPath);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            executeResult.setTask(task);
            dataCache.saveExecuteResult(command.getModelVersion(), executeResult);
            analyseDTO.setComplete(false);
        } else if (executeResult.getComplete()) {
            // 如果执行出错
            if (executeResult.getError() != null) {
                analyseDTO.setError(executeResult.getError());
            } else {
                // 此处表示执行完成，从库里读出执行结果
                analyseDTO.setModelList(executeResult.getModelList());
                analyseDTO.setSelectedList(executeResult.getSelectedList());
                analyseDTO.setHistoryList(executeResult.getHistoryList());
                analyseDTO.setSelectedModel(executeResult.getSelectedModel());
                analyseDTO.setAnalyseValues(executeResult.getAnalyseValues());
                analyseDTO.setAnalyseValue(executeResult.getAnalyseValues()[0]);
                analyseDTO.setDateList(executeResult.getDateList());
            }

            analyseDTO.setComplete(true);
            // 然后将内存中的executeResult移除
            dataCache.removeExecuteResult(command.getModelVersion());
        } else {
            // 此处表示正在执行
            analyseDTO.setComplete(false);
            analyseDTO.setProcess(executeResult.getProcess());
        }

        return analyseDTO;
    }

    public void checkModelAnalyseCommand(ModelAnalyseCommandDTO command) {
        Date startTime = command.getStartTime();
        Date endTime = command.getEndTime();
        //如果有一个为空，则
        if (startTime == null || endTime == null) {
            logger.error("输入时间有空值，不合法");
            throw new RuntimeException("输入时间有空值，不合法，获取电量模型预测数据失败");
        }
        //如果开始时间大于结束时间，
        if (startTime.compareTo(endTime) > 0) {
            logger.error("开始时间大于结束时间，输入不合法");
            throw new RuntimeException("开始时间大于结束时间，输入不合法，获取电量模型预测数据失败");
        }
    }

    private BigDecimal computeAnalyseValue(List<BigDecimal> source, List<BigDecimal> target) {
        assert source.size() == target.size();

//        double sum = 0.0;
//        for (int i = 0; i < target.size(); i++) {
//            sum += target.get(i);
//        }
//        double mean = sum / target.size();
//
//        // R方
//        double scoreTop = 0.0;
//        double scoreBottom = 0.0;
//        for (int i = 0; i < source.size(); i++) {
//            scoreTop += (source.get(i) - target.get(i)) * (source.get(i) - target.get(i));
//            scoreBottom += (target.get(i) - mean) * (target.get(i) - mean);
//        }
//
//        return DecimalFormatUtils.round(1 - scoreTop / scoreBottom, "0.00");

        //平均绝对百分比误差
        BigDecimal num = BigDecimal.valueOf(0.0);
        BigDecimal mae = BigDecimal.valueOf(0.0);
        int a = 0;        //记录有多少天实际值为0的无效数据
        for (int i = 0; i < source.size(); i++) {
            //if (target.get(i) == 0) {
            if (target.get(i).compareTo(BigDecimal.ZERO) == 0){
                a = a + 1;
                continue;
            }
            BigDecimal temp = source.get(i).divide(target.get(i),2, RoundingMode.HALF_UP).subtract(BigDecimal.ONE);
            //if(Math.abs((source.get(i) / target.get(i) - 1))>1)
            if (temp.abs().compareTo(BigDecimal.ONE) > 0)
                num = num.add(BigDecimal.ONE);
            else
                //num += Math.abs((source.get(i) / target.get(i) - 1));
                num = num.add(temp);
        }

        if (target.size()==a)   //每天的实际值都为0的情况
            //return DecimalFormatUtils.round(0.0, "0.00");
            return BigDecimal.ZERO;

        //mae = num/(target.size()-a);
        mae = num.divide(BigDecimal.valueOf(target.size()-a),2,RoundingMode.HALF_UP);

        //return DecimalFormatUtils.round(1-mae, "0.00");//误差越大，准确率越低
        return BigDecimal.ONE.subtract(mae);
    }

    @Override
    public void select(ModelSelectCommandDTO command) {
        String code = checkDeviceCode(command.getDeviceCode());
        List<Model> models = modelService.getAllModel(code, ModelTypeEnum.ELECTRICITY);
        Model select = null;

        for (Model model : models) {
            if (Objects.equals(model.getId(), command.getModelVersion())) {
                select = model;
            }
            model.setIsUsed(0);
        }

        if (select == null) {
            logger.error("未找到目标Model版本 modelVersion:{}", JSON.toJSONString(command.getModelVersion()));
            throw new RuntimeException("未找到目标Model版本");
        }

        select.setIsUsed(1);
        modelService.saveAllModel(models);
    }
}
